using System.Net;
using System.Net.Sockets;
using JPR.Common.Interfaces;
using JPR.TunelProxy.Interfaces;
using Microsoft.Practices.Unity;

namespace JPR.TunelProxy.Proxy.Implementations
{
	internal class SocketListener : IListener
	{
		#region Public properties

		#region Implementation of IListener

		public UnityContainer Container
		{
			get { return _container; }
			set { _container = value; }
		}

		#endregion

		#region Implementation of IListener

		public IConfigProvider ConfigProvider { get; set; }

		#endregion

		#endregion

		#region Private properties

		private readonly SocketAsyncEventArgs _socketAsyncEventArgs;
		private IClientDatagramProvider _clientDatagramProvider;
		private UnityContainer _container;

		#endregion

		#region Constructors

		public SocketListener()
		{
			_socketAsyncEventArgs = new SocketAsyncEventArgs();
			_socketAsyncEventArgs.Completed += AcceptCompleted;
			ConfigProvider = null;
		}

		public SocketListener(IConfigProvider configProvider)
		{
			ConfigProvider = configProvider;
		}

		#endregion

		#region Public methods

		#region Implementation of IListener

		public void ListenAndAcceptAsync()
		{
			Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			EndPoint ep = new IPEndPoint(IPAddress.Any, ConfigProvider.ListenerPort);
			socket.Bind(ep);
			socket.Listen(ConfigProvider.MaxNumberOfConnection);
			_socketAsyncEventArgs.UserToken = socket;
			socket.AcceptAsync(_socketAsyncEventArgs);
		}

		private void RunProxyWorker()
		{
			IProxyWorker proxyWorker =
				_container.Resolve<IProxyWorker>(new PropertyOverride("ClientDatagramProvider", _clientDatagramProvider));
			proxyWorker.ProcessCommunication();
		}

		#endregion

		#endregion

		#region Private methods

		private void AcceptCompleted(object sender, SocketAsyncEventArgs e)
		{
			if (e.SocketError != SocketError.Success)
			{
				throw new SocketException((int) e.SocketError);
			}
			Socket acceptedSocket = e.AcceptSocket;
			e.AcceptSocket = null;
			((Socket) e.UserToken).AcceptAsync(e);
			CreateClientNetworkSocketProvider(acceptedSocket);
			RunProxyWorker();
		}

		private void CreateClientNetworkSocketProvider(Socket acceptedSocket)
		{
			_clientDatagramProvider = _container.Resolve<IClientDatagramProvider>(new PropertyOverride("Socket", acceptedSocket));
		}

		#endregion
	}
}